%%%-------------------------------------------------------------------
%%% @author Filippo Pacini <pacini@sgconsulting.it>
%%% @copyright 2008 S.G. Consulting s.r.l.
%%%
%%% License :
%%% The contents of this file are subject to the Mozilla Public
%%% License Version 1.1 (the "License"); you may not use this file
%%% except in compliance with the License. You may obtain a copy of
%%% the License at http://www.mozilla.org/MPL/
%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and
%%% limitations under the License.
%%% The Initial Developer of the Original Code is S.G. Consulting
%%% srl. Portions created by S.G. Consulting s.r.l. are Copyright (C)
%%% 2007 S.G. Consulting srl. All Rights Reserved.
%%%
%%% @doc 
%%% <p>Form validator ewgi middleware module.</p>
%%%
%%% @end
%%%
%%%-------------------------------------------------------------------
-module(ewgi_fv).

-include_lib("ewgi/include/ewgi.hrl").

%% API
-export([handle_req/1,
         validate/2, 
         required/0, 
         integer/0, 
         float/0, 
         regexp/1, 
         email/0]).

%%--------------------------------------------------------------------
%% @spec handle_req(Ctx) -> Ctx1
%%
%% @doc Check for required values. Returns a closure of two 
%% arguments (Key and Data). When called the closure check if Key  
%% is present and not empty.
%% @end
%%--------------------------------------------------------------------
handle_req(Ctx) ->
    Data = case ewgi_api:method(Ctx) of
               'GET' -> ewgi_api:parse_qs(ewgi_api:qs(Ctx));
               _     -> ewgi_api:parse_post(read_all_data(Ctx))
           end,
    Rules = ewgi_api:get_data("ewgi_fw.rules", Ctx),
    case validate(Data, Rules) of
        ok -> 
            ewgi_api:set_response(#ewgi_response{status={200, "OK"}, headers=[], message_body=[]}, Ctx);
        {error, Errors} -> 
            %% redirect to the referer page adding error messsages
            Ref = ewgi_api:get_header(referer, Ctx),
            ewgi_api:set_response(#ewgi_response{status={303, "See Other"}, headers=[{location, Ref++"?"++ewgi_api:urlencode(Errors)}], message_body=[]}, Ctx)
    end.

%% TODO: Implement this!
read_all_data(_Ctx) ->
    [].


%%--------------------------------------------------------------------
%% @spec required() -> fun(Key::atom(), Data::proplist())
%%
%% @doc Check for required values. Returns a closure of two 
%% arguments (Key and Data). When called the closure check if Key  
%% is present and not empty.
%% @end
%%--------------------------------------------------------------------
required() ->
    Inner = fun(Key, Data) ->
		    case ewgi_api:get_value(Key, Data) of
			undefined ->
			    {error, {list_to_atom(Key), not_found}};
			_ ->
			    ok
		    end
	    end,
    Inner.

%%--------------------------------------------------------------------
%% @spec integer() -> fun(Key::atom(), Data::proplist())
%%
%% @doc Returns a closure of 2 args: Key and Data.
%% The closure check if Key value is an integer.
%% @end
%%--------------------------------------------------------------------
integer() ->
    fun(Key, Data) ->
	    case ewgi_api:get_value(Key, Data) of
		undefined ->
		    {error, {list_to_atom(Key), not_found}};
		Value ->
		    try list_to_integer(Value) of
			_Val ->
			    ok
		    catch
			error:_Reason ->
			    {error, {list_to_atom(Key), is_not_integer}}
		    end
	    end
    end.

%%--------------------------------------------------------------------
%% @spec float() -> fun(Key::atom(), Data::proplist())
%%
%% @doc Returns a closure of 2 args: Key and Data.
%% The closure check if Key value is a float.
%% @end
%%--------------------------------------------------------------------
float() ->
    fun(Key, Data) ->
	    case ewgi_api:get_value(Key, Data) of
		undefined ->
		    {error, {list_to_atom(Key), not_found}};
		Value ->
		    try list_to_float(Value) of
			_Val ->
			    ok
		    catch
			error:_Reason ->
			    {error, {list_to_atom(Key), is_not_float}}
		    end
	    end
    end.

%%--------------------------------------------------------------------
%% @spec regexp(RegExp) -> fun(Key::atom(), Data::proplist())
%%
%% @doc Returns a closure of 2 args: Key and Data.
%% The closure check if Field value matches RegExp.
%% @end
%%--------------------------------------------------------------------
regexp(RegExp) ->
    fun(Key, Data) ->
	    case ewgi_api:get_value(Key, Data) of
		undefined ->
		    ok;
                Value ->
		    case regexp:match(Value, RegExp) of
			{match, _Start, _End} ->
			    ok;
			nomatch ->
			    {error, {list_to_atom(Key), {RegExp, no_match}}}
		    end
	    end
    end.

%%--------------------------------------------------------------------
%% @spec email() -> fun(Key::atom(), Data::proplist())
%%
%% @doc Returns a closure of 2 args: Key and Data.
%% The closure check if Key value is a valid email address.
%% @end
%%--------------------------------------------------------------------
email() ->
    regexp("^.+@.+\..+$").


%%====================================================================
%% Internal functions
%%====================================================================
%%
%% @spec validate(Data::proplist(), Rules::[rule()]) -> ok|{error, Errors}
%%
%% @doc Data is a list of parsed GET or POST parameters. 
%% Rules a list of Rules to check.
%% e.g. 
%% Rules = [{username, [required()]},
%%         {password, [required()]},
%%         {user_id, [required(), is_integer()]},
%%         {email, [required(), regex(".+@.+\..*")]}].
%% each rule (required, integer, regex, etc..) is a fun/2 or a closure 
%% of two arguments: Key to check and Data.
%% The closure returns ok if the test is passed, {error, Reason} on failure.
%% @end
%%
validate(Data, Rules) ->
    validate(Data, Rules, []).

validate(_Data, [], Errors) ->
    case Errors of
	[] ->
	    ok;
	_ ->
	    {error, Errors}
    end;
validate(Data, [{Key, Rules}|Rest], Errors) ->
    case check_rules(Key, Data, Rules) of
	ok ->
	    validate(Data, Rest, Errors);
	{error, Reason} ->
	    validate(Data, Rest, [Reason|Errors])
    end.

check_rules(Key, Data, Rules) ->
    check_rules(Key, Data, Rules, []).

check_rules(_Key, _Data, [], Errors) ->
    case Errors of
	[] ->
	    ok;
	_ ->
	    {error, Errors}
    end;
check_rules(Key, Data, [Rule|Rest], Errors) ->
    case Rule(Key, Data) of
	ok ->
	    check_rules(Key, Data, Rest, Errors);
	{error, Reason} ->
	    check_rules(Key, Data, Rest, [Reason|Errors])
    end.
